diff options
| author | Factiven <[email protected]> | 2023-04-25 16:29:00 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-04-25 16:29:00 +0700 |
| commit | 9b524cd5312dc01f955046f62cd40da26160a327 (patch) | |
| tree | 9f1d172b60d482dea8268742ffce7de1f7aea9ca /pages/anime/[...id].js | |
| parent | Update [...id].js (diff) | |
| parent | Merge branch 'main' into pre-production (diff) | |
| download | moopa-9b524cd5312dc01f955046f62cd40da26160a327.tar.xz moopa-9b524cd5312dc01f955046f62cd40da26160a327.zip | |
Merge pull request #1 from DevanAbinaya/pre-production
Merge branch pre-production to main
Diffstat (limited to 'pages/anime/[...id].js')
| -rw-r--r-- | pages/anime/[...id].js | 1064 |
1 files changed, 577 insertions, 487 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js index eb562cc..cf70c62 100644 --- a/pages/anime/[...id].js +++ b/pages/anime/[...id].js @@ -1,218 +1,463 @@ -import React, { useEffect, useState } from "react"; -import { META } from "@consumet/extensions"; +import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import "react-loading-skeleton/dist/skeleton.css"; -import Link from "next/link"; -import Layout from "../../components/layout"; -import Head from "next/head"; +import { ClockIcon, HeartIcon } from "@heroicons/react/20/solid"; +import { + TvIcon, + ArrowTrendingUpIcon, + RectangleStackIcon, +} from "@heroicons/react/24/outline"; -import { closestMatch } from "closest-match"; +import Head from "next/head"; +import Image from "next/image"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Layout from "../../components/layout"; +import Link from "next/link"; import Content from "../../components/hero/content"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../api/auth/[...nextauth]"; -import Image from "next/image"; +import { useSession } from "next-auth/react"; + +const query = ` + query ($username: String, $status: MediaListStatus) { + MediaListCollection(userName: $username, type: ANIME, status: $status, sort: SCORE_DESC) { + user { + id + name + about (asHtml: true) + createdAt + avatar { + large + } + statistics { + anime { + count + episodesWatched + meanScore + minutesWatched + } + } + bannerImage + mediaListOptions { + animeList { + sectionOrder + } + } + } + lists { + status + name + entries { + id + mediaId + status + progress + score + media { + id + status + title { + english + romaji + } + episodes + coverImage { + large + } + } + } + } + } + } + `; + +const infoQuery = `query ($id: Int) { + Media(id: $id) { + id + type + title { + romaji + english + native + } + coverImage { + extraLarge + large + color + } + bannerImage + description + episodes + nextAiringEpisode { + episode + airingAt + } + averageScore + popularity + status + startDate { + year + } + duration + genres + relations { + edges { + relationType + node { + id + type + status + title { + romaji + english + userPreferred + } + coverImage { + extraLarge + large + color + } + } + } + } + recommendations { + nodes { + mediaRecommendation { + id + title { + romaji + } + coverImage { + extraLarge + large + } + } + } + } + } +}`; + +export default function Info() { + const { data: session, status } = useSession(); + const [data, setData] = useState(null); + const [info, setInfo] = useState(null); + const [episode, setEpisode] = useState(null); + const [loading, setLoading] = useState(false); + const [progress, setProgress] = useState(null); + const [statuses, setStatuses] = useState(null); + const [stall, setStall] = useState(false); + const [color, setColor] = useState(null); -export default function Himitsu({ - info, - color, - episodeList, - episode1, - sessions, - progress, - status, - lastPlayed, - stall, -}) { - const [showText, setShowtext] = useState(false); - const [load, setLoad] = useState(true); const [showAll, setShowAll] = useState(false); - const [time, setTime] = useState(0); - const episode = episodeList; - const epi1 = episode1; + const [time, setTime] = useState(0); + const { id } = useRouter().query; - const maxItems = 3; + const rec = info?.recommendations?.nodes.map( + (data) => data.mediaRecommendation + ); - const nextAir = info.nextAiringEpisode; - // console.log(time); + // console.log(rec); + // console.log(info); useEffect(() => { - if (nextAir) { - setTime(convertSecondsToTime(nextAir.timeUntilAiring)); - } - - function getBrightness(color) { - const rgb = color.match(/\d+/g); - return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; - } + const defaultState = { + data: null, + info: null, + episode: null, + loading: true, + statuses: null, + progress: null, + }; - // set the text color based on the background color - function setTextColor(element) { - const backgroundColor = getComputedStyle(element).backgroundColor; - const brightness = getBrightness(backgroundColor); - if (brightness < 128) { - element.style.color = "#fff"; // white + // Reset all state variables to their default values + Object.keys(defaultState).forEach((key) => { + const value = defaultState[key]; + if (Array.isArray(value)) { + value.length + ? eval( + `set${ + key.charAt(0).toUpperCase() + key.slice(1) + }(${JSON.stringify(value)})` + ) + : eval(`set${key.charAt(0).toUpperCase() + key.slice(1)}([])`); } else { - element.style.color = "#000"; // black + eval( + `set${key.charAt(0).toUpperCase() + key.slice(1)}(${JSON.stringify( + value + )})` + ); } - } - - const elements = document.querySelectorAll(".dynamic-text"); - elements.forEach((element) => { - setTextColor(element); }); + async function fetchData() { + if (id) { + setLoading(false); + try { + const [res, info] = await Promise.all([ + fetch(`https://api.moopa.my.id/meta/anilist/info/${id?.[0]}`), + fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: infoQuery, + variables: { + id: id?.[0], + }, + }), + }), + ]); + const data = await res.json(); + const infos = await info.json(); + setInfo(infos.data.Media); + + const textColor = setTxtColor(infos.data.Media.coverImage?.color); + + if (!data || data.episodes.length === 0) { + const res = await fetch( + `https://api.consumet.org/meta/anilist/info/${id[0]}?provider=9anime` + ); + const datas = await res.json(); + setColor({ + backgroundColor: `${data?.color || "#ffff"}`, + color: textColor, + }); + setStall(true); + setEpisode(datas.episodes); + } else { + setEpisode(data.episodes); + } + + setColor({ + backgroundColor: `${data?.color || "#ffff"}`, + color: textColor, + }); + + if (session?.user?.name) { + const response = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: query, + variables: { + username: session?.user?.name, + }, + }), + }); + + const dat = await response.json(); + + const prog = dat.data.MediaListCollection; + + const gat = prog.lists.map((item) => item.entries); + const git = gat.map((item) => + item.find((item) => item.media.id === parseInt(data?.id)) + ); + const gut = git?.find( + (item) => item?.media.id === parseInt(data?.id) + ); + + if (gut) { + setProgress(gut?.progress); + if (gut.status === "CURRENT") { + setStatuses("Watching"); + } else if (gut.status === "PLANNING") { + setStatuses("Planned to watch"); + } else if (gut.status === "COMPLETED") { + setStatuses("Completed"); + } else if (gut.status === "DROPPED") { + setStatuses("Dropped"); + } else if (gut.status === "PAUSED") { + setStatuses("Paused"); + } else if (gut.status === "REPEATING") { + setStatuses("Rewatching"); + } + } + } + + if (data.nextAiringEpisode) { + setTime( + convertSecondsToTime(data.nextAiringEpisode.timeUntilAiring) + ); + } - setLoad(false); - }, [color, sessions, info.id]); + setData(data); + setLoading(true); + } catch (error) { + console.log(error); + setTimeout(() => { + window.location.reload(); + }, 1000); + } + } + } + fetchData(); + }, [id, session?.user?.name]); return ( <> <Head> - <title>{info.title?.english || info.title.romaji}</title> - <meta name="detail" content="Detail about the Anime" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" href="/c.svg" /> + <title> + {info + ? info?.title?.romaji || info?.title?.english + : "Retrieving Data..."} + </title> </Head> - - <Layout navTop="text-white bg-primary md:pt-0 md:px-0 bg-slate bg-opacity-40"> - <div className="text static bg-primary flex w-screen flex-col justify-center pt-nav md:pt-1 pb-10"> - <div className="pointer-events-none absolute top-0 left-0"> - <div className="absolute bg-gradient-to-t w-screen z-20 top-0 md:h-[300px] h-[420px] from-10% from-primary to-transparent" /> - <img - // ref={ref} - src={info.cover || info.image} - className="md:h-[300px] h-[420px] w-screen object-cover brightness-[60%]" - /> - </div> - {info ? ( - <div className="flex flex-col items-center gap-10"> - <div className="flex w-screen flex-col gap-10 md:w-[70%]"> - <div className="z-40 flex flex-col gap-10 px-5 pt-[7rem] md:flex-row lg:mt-[5rem] lg:px-0"> - <div className="flex gap-5 md:h-[250px] md:w-52"> - <div className="flex h-[200px] w-52 bg-[#dadada50] md:h-[250px] md:w-full"> - {info.image && ( - <> - <div - key={info.id} - // src={info.image} - className="" - > - <Image - src={info.image} - alt="image" - width={500} - height={500} - draggable={false} - className="object-cover h-[200px] w-[200px] md:h-[250px] shrink-0 bg-image shadow-md" - /> - </div> - </> - )} - </div> - - {/* MOBILE */} - <div className="w-full grid place-items-stretch gap-3 lg:hidden "> - <h1 className="shrink-0 text-xl font-semibold line-clamp-2"> - {info.title.romaji || info.title.english} - </h1> - <div className="flex w-[90%] flex-col gap-1"> - <div className="flex gap-2"> - <h1>Rate:</h1> - <p className="font-bold">{info.rating}%</p> - </div> - - <div className="flex w-[200px] gap-2"> - <h1>Format:</h1> - <p>{info.type}</p> + <SkeletonTheme baseColor="#232329" highlightColor="#2a2a32"> + <Layout navTop="text-white bg-primary md:pt-0 md:px-0 bg-slate bg-opacity-40 z-50"> + <div className="w-screen min-h-screen relative flex flex-col items-center bg-primary gap-5"> + <div className="bg-image w-screen"> + <div className="bg-gradient-to-t from-primary from-10% to-transparent absolute h-[300px] w-screen z-10 inset-0" /> + {info ? ( + <Image + src={ + info?.bannerImage || + info?.coverImage?.extraLarge || + info?.coverImage.large + } + alt="banner anime" + height={1000} + width={1000} + className="object-cover bg-image w-screen absolute top-0 left-0 h-[300px] brightness-[70%] z-0" + /> + ) : ( + <div className="bg-image w-screen absolute top-0 left-0 h-[300px]" /> + )} + </div> + <div className="lg:w-[70%] md:pt-[10rem] z-30 flex flex-col gap-5"> + {/* Mobile */} + + <div className="md:hidden pt-5 w-screen px-5 flex flex-col"> + <div className="h-[250px] flex flex-col gap-1 justify-center"> + <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]"> + {/* Yuru Campā³ SEASON 2 */} + {info?.title?.romaji || info?.title?.english} + </h1> + <p + className="line-clamp-2 text-sm font-light antialiased w-[56%]" + dangerouslySetInnerHTML={{ __html: info?.description }} + /> + <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]"> + {info?.genres + ?.slice( + 0, + info?.genres?.length > 3 ? info?.genres?.length : 3 + ) + .map((item, index) => ( + <span + key={index} + className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full" + // style={color} + > + <span className="">{item}</span> + {/* {index !== info?.genres?.length - 1 && ( + <span className="w-[5px] h-[5px] ml-[6px] mb-[2px] inline-block rounded-full bg-white" /> + )} */} + </span> + ))} + </div> + {info && ( + <div className="flex items-center gap-5 pt-3 text-center"> + <div className="flex items-center gap-2 text-center"> + <div className="bg-action px-10 rounded-sm font-karla font-bold"> + {statuses ? statuses : "Add to List"} </div> - - <div className="flex gap-2"> - <h1>Status:</h1> - <p>{info.status}</p> + <div className="h-6 w-6"> + <HeartIcon /> </div> - - {/* {nextAir && ( - <div className="flex gap-2"> - <h1>Ep {nextAir.episode}:</h1> - <p>{time}</p> - </div> - )} */} - </div> - <div className="flex"> - {epi1 && epi1[0] ? ( - <Link href={`/anime/watch/${epi1[0].id}/${info.id}`}> - <h1 className="flex cursor-pointer items-center gap-2 px-1 py-2 font-bold text-[#ffffff]"> - <svg - xmlns="http://www.w3.org/2000/svg" - width="13" - height="12" - fill="none" - viewBox="0 0 250 289" - > - <path - fill="#fff" - d="M249.734 144.5l-249 143.761V.741l249 143.759z" - ></path> - </svg>{" "} - WATCH - </h1> - </Link> - ) : ( - <h1 className="pointer-events-none flex items-center gap-2 px-1 py-2 font-bold text-[#ffffffa5]"> - <svg - xmlns="http://www.w3.org/2000/svg" - width="13" - height="12" - className="fill-[#ffffff8d]" - viewBox="0 0 250 289" - > - <path d="M249.734 144.5l-249 143.761V.741l249 143.759z"></path> - </svg>{" "} - WATCH - </h1> - )} </div> </div> + )} + </div> + <div className="bg-secondary rounded-sm h-[30px]"> + <div className="flex items-center justify-center h-full gap-10 p-2"> + {info && info.status !== "NOT_YET_RELEASED" ? ( + <> + <div className="flex-center gap-2"> + <TvIcon className="w-5 h-5 text-action" /> + <h4 className="font-karla">{info?.type}</h4> + </div> + <div className="flex-center gap-2"> + <ArrowTrendingUpIcon className="w-5 h-5 text-action" /> + <h4>{info?.averageScore}%</h4> + </div> + <div className="flex-center gap-2"> + <RectangleStackIcon className="w-5 h-5 text-action" /> + <h1>{info?.episodes} Episodes</h1> + </div> + </> + ) : ( + <div>{info && "Not Yet Released"}</div> + )} </div> + </div> + </div> + + {/* PC */} + <div className="hidden md:flex gap-5 w-full flex-nowrap"> + <div className="shrink-0 md:h-[250px] md:w-[180px] w-[115px] h-[164px] relative"> + {info ? ( + <> + <div className="bg-image md:h-[250px] md:w-[180px] w-[115px] h-[164px] bg-opacity-30 absolute backdrop-blur-lg z-10" /> + <Image + src={ + info.coverImage.extraLarge || info.coverImage.large + } + alt="poster anime" + height={700} + width={700} + className="object-cover md:h-[250px] md:w-[180px] w-[115px] h-[164px] z-20 absolute" + /> + </> + ) : ( + <Skeleton className="h-[250px] w-[180px]" /> + )} + </div> - {/* PC */} - <div className="w-full flex-col gap-5 md:flex"> - <div className="hidden flex-col gap-5 lg:flex"> - <h1 className="text-4xl font-bold"> - {info.title?.english || - info.title.romaji || - info.title.native} - </h1> + {/* PC */} + <div className="hidden md:flex w-full flex-col gap-5 px-3 h-[250px]"> + <div className="flex flex-col gap-2"> + <h1 className=" font-inter font-bold text-[36px] text-white line-clamp-1"> + {info ? ( + info?.title?.romaji || info?.title?.english + ) : ( + <Skeleton width={450} /> + )} + </h1> + {info ? ( <div className="flex gap-6"> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} style={color} > - {episode && episode.length} Episodes + {info?.episodes} Episodes </div> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} style={color} > - {info.releaseDate} + {info?.startDate?.year} </div> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} style={color} > - {info.rating}% + {info?.averageScore}% </div> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} style={color} > - {info.type} + {info?.type} </div> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} style={color} > - {info.status} + {info?.status} </div> <div className={`dynamic-text rounded-md px-2 font-karla font-bold`} @@ -220,390 +465,219 @@ export default function Himitsu({ > Sub | EN </div> - {nextAir && ( + {info && info.nextAiringEpisode && ( <div className={`dynamic-text shadow-button rounded-md px-2 font-karla font-bold`} style={color} > - Ep {nextAir.episode}: {time} + Ep {info.nextAiringEpisode.episode}: {time} </div> )} </div> - </div> - <div - className={`hidden h-[140px] transition-all duration-300 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`} - > - <p - dangerouslySetInnerHTML={{ __html: info.description }} - className="mr-5" - /> - </div> - <div className="lg:hidden text-sm text-txt"> - <p - className={`${showText ? "" : "line-clamp-3"}`} - dangerouslySetInnerHTML={{ - __html: info.description, - }} - /> - <button - onClick={() => setShowtext(!showText)} - className="font-rama font-bold text-white" - > - {showText ? " Show Less" : " Show More"} - </button> - </div> + ) : ( + <Skeleton width={240} height={32} /> + )} </div> + {info ? ( + <p + dangerouslySetInnerHTML={{ __html: info?.description }} + className="overflow-y-scroll scrollbar-thin pr-2 scrollbar-thumb-secondary scrollbar-thumb-rounded-lg h-[140px]" + /> + ) : ( + <Skeleton className="h-[130px]" /> + )} + {/* <p>{data.description}</p> */} </div> + </div> - <div className=""> - <div className="flex gap-5 items-center"> + <div> + <div className="flex gap-5 items-center"> + {info && ( <div className="p-3 lg:p-0 text-[20px] md:text-2xl font-bold font-karla"> Relations </div> - {info.relations.length > maxItems && ( - <div - className="cursor-pointer" - onClick={() => setShowAll(!showAll)} - > - {showAll ? "show less" : "show more"} - </div> - )} - </div> - <div - className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-10 rounded-xl`} - > - {info.relations && - info.relations - .slice(0, showAll ? info.relations.length : maxItems) - .map((relation, index) => { + )} + {info?.relations?.edges?.length > 3 && ( + <div + className="cursor-pointer" + onClick={() => setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} + </div> + )} + </div> + <div + className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-4 rounded-xl`} + > + {info?.relations?.edges + ? info?.relations?.edges + .slice(0, showAll ? info?.relations?.edges.length : 3) + .map((r, index) => { + const rel = r.node; return ( <Link - key={relation.id} + key={rel.id} href={ - relation.type === "TV" || - relation.type === "OVA" || - relation.type === "MOVIE" || - relation.type === "SPECIAL" || - relation.type === "ONA" - ? `/anime/${relation.id}` + rel.type === "ANIME" || + rel.type === "OVA" || + rel.type === "MOVIE" || + rel.type === "SPECIAL" || + rel.type === "ONA" + ? `/anime/${rel.id}` : `/manga/detail/id?aniId=${ - relation.id + rel.id }&aniTitle=${encodeURIComponent( - info.title?.english || - info.title.romaji || - info.title.native + info?.title?.english || + info?.title.romaji || + info?.title.native )}` } className={`hover:scale-[1.02] hover:shadow-lg md:px-0 px-4 scale-100 transition-transform duration-200 ease-out w-full ${ - relation.type === "MUSIC" + rel.type === "MUSIC" ? "pointer-events-none" : "" }`} > <div - key={relation.id} + key={rel.id} className="w-full shrink h-[126px] bg-secondary flex rounded-md" > <div className="w-[90px] bg-image rounded-l-md shrink-0"> - <img - src={relation.image} - alt={relation.id} + <Image + src={ + rel.coverImage.extraLarge || + rel.coverImage.large + } + alt={rel.id} + height={500} + width={500} className="object-cover h-full w-full shrink-0 rounded-l-md" /> </div> <div className="h-full grid px-3 items-center"> <div className="text-action font-outfit font-bold"> - {relation.relationType} + {r.relationType} </div> <div className="font-outfit font-thin line-clamp-2"> - {relation.title.romaji} + {rel.title.userPreferred || + rel.title.romaji} </div> - <div className={``}>{relation.type}</div> + <div className={``}>{rel.type}</div> </div> </div> </Link> ); - })} - </div> + }) + : [1, 2, 3].map((item) => ( + <div key={item} className="w-full"> + <Skeleton className="h-[126px]" /> + </div> + ))} </div> - - <div className="z-20 flex flex-col gap-10 p-3 lg:p-0"> - <div className="flex items-center md:gap-10 gap-7"> + </div> + <div className="z-20 flex flex-col gap-10 p-3 lg:p-0"> + <div className="flex items-center md:gap-10 gap-7"> + {info && ( <h1 className="text-[20px] md:text-2xl font-bold font-karla"> Episodes </h1> - <div className="flex items-center rounded-md"> - <button - // onClick={handleEnLang} - className={ - // Lang? - `w-14 p-1 rounded-l-md bg-secondary text-action shadow-action` - // `w-14 p-1 rounded-l-md bg-[#17171b] text-[#404040]` - } - > - EN - </button> - <div className="w-[1px] bg-white h-4" /> - <button - // onClick={handleIdLang} - className={ - // subIndo === null - // ? - `w-14 p-1 rounded-r-md bg-[#171717] text-[#404040] pointer-events-none` - // : Lang - // ? `w-14 p-1 rounded-r-md bg-[#171717] text-[#404040]` - // : `w-14 p-1 rounded-r-md bg-[#212121]` - } - > - ID - </button> - </div> - {status && ( - <> - <div className="font-karla relative group flex justify-center"> - {status} - <span className="absolute bottom-8 shadow-lg invisible group-hover:visible transition-all opacity-0 group-hover:opacity-100 font-karla font-light bg-secondary p-1 px-2 rounded-lg"> - status - </span> + )} + {info?.nextAiringEpisode && ( + <div className="flex items-center gap-2"> + <div className="flex items-center gap-4"> + <h1>Next Ep :</h1> + <div + className="px-5 rounded-sm font-karla font-bold bg-white text-black" + // style={color} + > + {time} </div> - </> - )} - </div> - <div className="flex h-[640px] flex-col gap-5 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> - {load ? ( - <p>Loading...</p> - ) : episode ? ( - episode.map((episode, index) => { - return ( - <div key={index} className="flex flex-col gap-3 px-2"> - <Link - href={`/anime/watch/${episode.id}/${info.id}/${ - stall ? `9anime` : "" - }`} - className={`text-start text-sm md:text-lg ${ - episode.number <= progress - ? "text-[#5f5f5f]" - : "text-white" - }`} + </div> + <div className="h-6 w-6"> + <ClockIcon /> + </div> + </div> + )} + {statuses && ( + <> + <div className="hidden font-karla relative group md:flex justify-center"> + {statuses} + <span className="absolute bottom-8 shadow-lg invisible group-hover:visible transition-all opacity-0 group-hover:opacity-100 font-karla font-light bg-secondary p-1 px-2 rounded-lg"> + status + </span> + </div> + </> + )} + </div> + {loading ? ( + data && ( + <div className="flex h-[640px] flex-col gap-5 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> + {episode ? ( + episode.map((epi, index) => { + return ( + <div + key={index} + className="flex flex-col gap-3 px-2" > - <p>Episode {episode.number}</p> - {episode.title && ( - <p - className={`text-xs md:text-sm ${ - episode.number <= progress - ? "text-[#5f5f5f]" - : "text-[#b1b1b1]" - } italic`} - > - "{episode.title}" - </p> + <Link + href={`/anime/watch/${epi.id}/${data.id}/${ + stall ? `9anime` : "" + }`} + className={`text-start text-sm md:text-lg ${ + progress && epi.number <= progress + ? "text-[#5f5f5f]" + : "text-white" + }`} + > + <p>Episode {epi.number}</p> + {epi.title && ( + <p + className={`text-xs md:text-sm ${ + progress && epi.number <= progress + ? "text-[#5f5f5f]" + : "text-[#b1b1b1]" + } italic`} + > + "{epi.title}" + </p> + )} + </Link> + {index !== episode?.length - 1 && ( + <span className="h-[1px] bg-white" /> )} - </Link> - <div className="h-[1px] bg-white" /> - </div> - ); - }) - ) : ( - <p>No Episodes Available</p> - )} + </div> + ); + }) + ) : ( + <p>No Episodes Available</p> + )} + </div> + ) + ) : ( + <div className="pb-10 w-full flex-center"> + Loading Episodes... </div> - </div> + )} </div> + </div> + {rec && ( <div className="w-screen md:w-[80%]"> <Content ids="recommendAnime" section="Recommendations" - data={info.recommendations} + data={rec} /> </div> - </div> - ) : ( - <div className="flex h-screen flex-col items-center justify-center gap-10 pb-52 "> - <h1 className="scale-150 font-roboto text-6xl text-red-400"> - 404 - </h1> - <p className="text-4xl font-semibold">{`> Woops.. I think we don't have that Anime :(`}</p> - <Link className="pt-10 text-2xl" href="/search/anime"> - Return to search - </Link> - </div> - )} - </div> - </Layout> + )} + <div></div> + <div></div> + </div> + </Layout> + </SkeletonTheme> </> ); } -export async function getServerSideProps(context) { - context.res.setHeader( - "Cache-Control", - "public, s-maxage=10, stale-while-revalidate=59" - ); - const session = await getServerSession(context.req, context.res, authOptions); - - const { id } = context.query; - if (!id) { - return { - notFound: true, - }; - } - - const provider = new META.Anilist(); - - const [info, episodes] = await Promise.all([ - fetch(`https://api.moopa.my.id/meta/anilist/info/${id[0]}`).then((res) => - res.json() - ), - provider.fetchEpisodesListById(id[0]), - ]); - - if (!info) { - return { - notFound: true, - }; - } - - let episodeList = episodes; - let stall = false; - - if (episodes.length === 0) { - const res = await fetch( - `https://api.consumet.org/meta/anilist/info/${id[0]}?provider=9anime` - ); - const data = await res.json(); - episodeList = data.episodes; - stall = true; - } - - let progress = null; - let status = null; - let lastPlayed = null; - - if (session) { - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: ` - query ($username: String, $status: MediaListStatus) { - MediaListCollection(userName: $username, type: ANIME, status: $status, sort: SCORE_DESC) { - user { - id - name - about (asHtml: true) - createdAt - avatar { - large - } - statistics { - anime { - count - episodesWatched - meanScore - minutesWatched - } - } - bannerImage - mediaListOptions { - animeList { - sectionOrder - } - } - } - lists { - status - name - entries { - id - mediaId - status - progress - score - media { - id - status - title { - english - romaji - } - episodes - coverImage { - large - } - } - } - } - } - } - `, - variables: { - username: session?.user.name, - }, - }), - }); - - const dat = await response.json(); - - // const resp = await fetch(`/api/get-user?userName=${session?.user.name}`); - // const data = await resp.json(); - - lastPlayed = session?.user?.recentWatch?.filter( - (item) => item.title.romaji === info.title.romaji - )[0]?.episode; - - const prog = dat.data.MediaListCollection; - - const gat = prog.lists.map((item) => item.entries); - const git = gat.map((item) => - item.find((item) => item.media.id === parseInt(info.id)) - ); - const gut = git?.find((item) => item?.media.id === parseInt(info.id)); - - if (gut) { - progress = gut?.progress; - if (gut.status === "CURRENT") { - status = "Watching"; - } else if (gut.status === "PLANNING") { - status = "Planned to watch"; - } else if (gut.status === "COMPLETED") { - status = "Completed"; - } else if (gut.status === "DROPPED") { - status = "Dropped"; - } else if (gut.status === "PAUSED") { - status = "Paused"; - } else if (gut.status === "REPEATING") { - status = "Rewatching"; - } - } - } - - const color = { backgroundColor: `${info.color || 'white'}` }; - const epi1 = episodes.filter((epi) => epi.number === 1); - const title = info.title?.userPreferred || "No Title"; - - return { - props: { - info: { - ...info, - title: { - ...info.title, - userPreferred: title, - }, - }, - color, - episodeList, - episode1: epi1, - sessions: session, - progress: progress || null, - status: status, - lastPlayed: lastPlayed || null, - stall, - }, - }; -} - function convertSecondsToTime(sec) { let days = Math.floor(sec / (3600 * 24)); let hours = Math.floor((sec % (3600 * 24)) / 3600); @@ -625,3 +699,19 @@ function convertSecondsToTime(sec) { return time.trim(); } + +function getBrightness(hexColor) { + if (!hexColor) { + return 200; + } + const rgb = hexColor + .match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i) + .slice(1) + .map((x) => parseInt(x, 16)); + return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; +} + +function setTxtColor(hexColor) { + const brightness = getBrightness(hexColor); + return brightness < 150 ? "#fff" : "#000"; +} |